package com.hardy.basic;

import com.google.common.util.concurrent.RateLimiter;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Author: Hardy
 * Date:   2021/3/10
 * Description:
 **/
public class FlowLimit {

    public static void sleep(long ms) {
        try {
            TimeUnit.MILLISECONDS.sleep(ms);
        } catch (InterruptedException ignored) {
        }
    }

    public static void main(String[] args) {
        LeakyBucket main = new LeakyBucket();

        long s = System.currentTimeMillis();
        for (int i = 0; i < 200; i++) {
            sleep(1);
            if (main.tryAcquire()) {
                System.out.println((0 - s) + (s = System.currentTimeMillis()));
            }
        }
    }


    // 固定窗口计数器 原理版 未考虑并发
    static class Counter {
        private int threshold = 100;        // 可以通过的请求上限
        private long timeInterval = 1000;   // 计数的时间区间大小，1000ms

        private long startTime = 0;         // 起始时间
        private AtomicInteger counter = new AtomicInteger(0); // 计数器

        public boolean tryAcquire() {
            long cur = System.currentTimeMillis();
            // 判断计数器超时
            if (cur > startTime + timeInterval) {
                startTime = cur;
                counter.set(0);  // 超时重置计数器
            }
            // 判断有没有超限制
            return counter.incrementAndGet() <= threshold;
        }
    }

    // 漏桶法 原理版 未考虑并发，正常需要按照 rate配置 转发
    static class LeakyBucket {
        private int rate = 1;         // 出水速率，通过请求的速率 1/ms
        private int fixed = 10;       // 每次加水量，
        private long capacity = 10;   // 容量
        private long lastTime = 0;    // 最后一次注水时间
        private long watter = 0;      // 当前水量

        public boolean tryAcquire() {
            long cur = System.currentTimeMillis();
            // 计算期间流水流水量
            long out = (cur - lastTime) * rate;   // 当前时间 - 上次注水时间 * 流水速率 = 这段时间流出的水量
            long left = Math.max(0, watter - out); // 计算当前剩余水量

            if (left + fixed > capacity) return false;      // 剩余水量 + 一滴 > 容量 注水失败

            lastTime = cur;         // 更新注水时间
            watter = left + fixed;  // 更新水量
            return true;
        }
    }

    // 令牌桶 原理版 未考虑并发
    static class TokenBucket {
        private float rate = 0.1f;    // 令牌放入效率，通过请求的速率
        private long capacity = 100;  // 令牌容量
        private long lastTime = 0;    // 最后一次获取令牌时间
        private long tokens = capacity;   // 当前令牌数量

        public boolean tryAcquire() {
            long cur = System.currentTimeMillis();
            // 添加令牌
            long in = (long) ((cur - lastTime) * rate); // 当前时间 - 上次添加之间 * 添加速率 = 这段时间需添加的令牌数
            long left = Math.min(capacity, tokens + in);// 计算当前剩余令牌数量

            if (left - 1 < 0) return false;  // 扣除令牌

            lastTime = cur;     // 更新添加令牌时间
            tokens = left - 1;  // 更新令牌数量
            return true;
        }
    }

    static class TokenBucketGuava {
        RateLimiter rateLimiter = RateLimiter.create(100);

        public boolean tryAcquire() {
            return rateLimiter.tryAcquire();
        }
    }

    // 滑动窗口
    class SlideWin {
        // 略...
    }
}

